﻿namespace AlgoHero.Modelo
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Xml;
    using System.Linq;
    using System.Text;

    
    public class MapeoDeTeclas
    {
        /* Esta clase representa un mapeo de notas a teclas. Implementada a traves de un Dictionary cuyas
         * clave son los nombres de las notas y sus valores son una coleccion de teclas. Estas colecciones
         * deben implementar la interfaz IList provista por el lenguaje.
         * Las teclas se representan mediante constantes enumeradas definidas en System.Windows.Input
         */
 
        #region Atributos

        private IDictionary< string, IList<Tecla> > mapeo;
        private static string[] NombresDeNotas = { "DO", "DO#", "RE", "RE#", "MI", "FA", "FA#",
                                                   "SOL", "SOL#", "LA", "LA#", "SI", "SILENCIO" };

        #endregion

        #region Constructor

        public MapeoDeTeclas()
        {           
            /* Crea una mapeo de teclas con todas las notas musicales válidas, y sin teclas asociadas.
             */
            this.mapeo = new Dictionary< string, IList<Tecla> >();

            foreach (string nombre in NombresDeNotas)
            {
                this.mapeo.Add(nombre, new List<Tecla>());
            }
        }

        public MapeoDeTeclas(XmlElement nodo)
        {
            XmlNodeList listaNodoClaves = nodo.ChildNodes;

            this.mapeo = new Dictionary<string, IList<Tecla>>();
            
            foreach (XmlElement nodoClave in listaNodoClaves)
            {
                XmlNodeList listaNodoTecla = nodoClave.ChildNodes;

                string nombreClave = nodoClave.Attributes.GetNamedItem("nombredenota").Value;
                this.mapeo.Add(nombreClave, new List<Tecla>());

                foreach (XmlElement nodoTecla in listaNodoTecla)
                {
                    this.mapeo[nombreClave].Add(new Tecla(nodoTecla));
                }
            }
        }

        #endregion

        #region Métodos

        public IList<Tecla> CalcularTeclasDiferentes()
        {

            List<Tecla> teclasDiferentes = new List<Tecla>();
            foreach (IList<Tecla> teclas in this.mapeo.Values)
            {
                foreach (Tecla tecla in teclas)
                {
                    if (!teclasDiferentes.Contains(tecla))
                    {
                        teclasDiferentes.Add(tecla);
                    }
                }
            }

            return teclasDiferentes;
        }

        public void AddTeclasPara(string nombreDeNota, IList<Tecla> teclas)
        {
            /* Agrega al mapeo las teclas asociadas a la nota cuyo nombre es 'nombreDeNota'.
             * 
             * Precondiciones: nombreDeNota deber ser un nombre de nota musical válido.
             * 
             * Postcondiciones: Si nombreDeNota es inválido, lanza NotaInvalidaException.
             */
                        
            string nombreEnLimpio = ValidarNombre(nombreDeNota);                        

            //Obtenemos la lista de teclas asociada a la nota
            IList<Tecla> listaTeclas = this.mapeo[nombreEnLimpio];            
                        
            //Agregamos las teclas que correspondan
            foreach (Tecla tecla in teclas)
            {
                // Si alguna de las teclas ya se encontraba en el mapeo, no la agrega nuevamente
                if (!listaTeclas.Contains(tecla))
                {
                    listaTeclas.Add(tecla);
                }
            }      
        }

        private string ValidarNombre(string unNombreDeNota)
        {
            /* Verifica si el nombre de la nota es válido. Si lo es, devuelve el nombre sin espacios en blanco de mas y
             * en mayusculas. Si no lo es, lanza NotaInvalidaException.
             */
 
            string nombreEnLimpio = unNombreDeNota.Trim().ToUpper();

            if (!NombresDeNotas.Contains(nombreEnLimpio))
            {
                throw new NotaInvalidaException("El nombre de la nota es invalido.");
            }

            return nombreEnLimpio;
        }

        public IList<Tecla> GetTeclasPara(string nombreDeNota)
        {
            /* Devuelve las teclas asociadas a la nota cuyo nombre es 'nombreDeNota'.
             * 
             * Precondiciones: nombreDeNota deber ser un nombre de nota musical válido.
             * 
             * Postcondiciones: Si nombreDeNota es inválido, lanza NotaInvalidaException.
             */

            string nombreEnLimpio = ValidarNombre(nombreDeNota);                        

            return (this.mapeo[nombreEnLimpio]);
        }

        public void EliminarTeclasPara(string nombreDeNota, IList<Tecla> teclas)
        {
            /* Elimina del mapeo las teclas asociadas a la nota 'nombreDeNota' que sean iguales
             * a las teclas contenidas en 'teclas'.
             * 
             * Precondiciones: nombreDeNota deber ser un nombre de nota musical válido.
             * 
             * Postcondiciones: Si nombreDeNota es inválido, lanza NotaInvalidaException.
             */

            string nombreEnLimpio = ValidarNombre(nombreDeNota);

            IList<Tecla> listaTeclas = this.mapeo[nombreEnLimpio];

            foreach (Tecla tecla in teclas)
            {
                // si 'tecla' no está asociada a la nota, no hace nada
                if (listaTeclas.Contains(tecla))
                {
                    listaTeclas.Remove(tecla);
                }
            }                
        }

        public void EliminarTeclasPara(string nombreDeNota)
        {
            /* Elimina del mapeo todas las teclas asociadas a la nota 'nombreDeNota'.
             * 
             * Precondiciones: nombreDeNota deber ser un nombre de nota musical válido.
             * 
             * Postcondiciones: Si nombreDeNota es inválido, lanza NotaInvalidaException.
             */

            string nombreEnLimpio = ValidarNombre(nombreDeNota);

            this.mapeo[nombreEnLimpio].Clear();
        }

        public bool TieneTeclas() 
        {
            /* Devuelve false si ninguna nota en el mapeo tiene teclas asociadas.
             * Devuelve true si alguna nota en el mapeo tiene alguna tecla asociada.
             */

            foreach (IList<Tecla> teclas in this.mapeo.Values)
            {
                if (teclas.Count != 0)
                {
                    return true;
                }
            }

            return false;
        }

        internal void SerializarEn(XmlDocument documento, XmlNode nodoPadre)
        {
            XmlNode nodoMapeoDeTeclas = documento.CreateNode(XmlNodeType.Element, "MapeoDeTeclas", "");

            foreach (KeyValuePair<string, IList<Tecla>> parClaveValor in this.mapeo)
            {
                XmlNode nodoClave = documento.CreateNode(XmlNodeType.Element, "Clave", "");
                XmlAttribute nombreDeNota = documento.CreateAttribute("nombredenota");

                string clave = parClaveValor.Key;
                nombreDeNota.Value = clave;
                nodoClave.Attributes.Append(nombreDeNota);

                IList<Tecla> listaTeclas = parClaveValor.Value;

                foreach (Tecla tecla in listaTeclas)
                {
                    tecla.SerializarEn(documento, nodoClave);
                }

                nodoMapeoDeTeclas.AppendChild(nodoClave);
            }

            nodoPadre.AppendChild(nodoMapeoDeTeclas);
        }
    


                

        #endregion

        
    }
}
